/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.persistence.dao;

import java.lang.reflect.Field;

import org.telosys.persistence.TelosysPersistenceException;
import org.telosys.persistence.mappers.FieldMapperInteger;
import org.telosys.persistence.mappers.FieldMapperLong;
import org.telosys.persistence.mappers.FieldMapperPrimitiveInt;
import org.telosys.persistence.mappers.FieldMapperPrimitiveLong;
import org.telosys.persistence.mapping.AttributeMapping;

/**
 * This class provides the internal mapping "object field - table column" <br>
 * It provides the data transfer between the field and the column ( bean to table, table to bean )<br>
 * and the field value setting <br>
 * Each operation is delegate to a "specialized field mapper" according to the field type.  
 *  
 * @author Laurent GUERIN
 *
 */
public class FieldMapper 
{
	private final Class<?>    entityClass ;

	private final  String     dbColumnName ;
	
	private final  String     fieldName ;
	
	private final  boolean    primaryKey ;
	
	private final int         keyPart  ;
	
	private final boolean     autoIncrementedColumn  ;
	
	private final Field       field ;  // Reflection API Field
	
	private final SpecializedFieldMapper specializedFieldMapper ;
	
	public FieldMapper( Class<?> entityClass, Field field, AttributeMapping mappingDefinition, SpecializedFieldMapper specializedFieldMapper ) 
	{
		super();
		this.entityClass = entityClass ;
		this.field = field ;
		//this.index  = index;
		this.dbColumnName  = mappingDefinition.getColumnName() ;
		this.fieldName = mappingDefinition.getAttributeName() ;
		this.primaryKey = mappingDefinition.isPrimaryKey() ;
		this.autoIncrementedColumn = mappingDefinition.isAutoIncremented() ;
		this.keyPart = mappingDefinition.getKeyPart();
		this.specializedFieldMapper = specializedFieldMapper ;
	}
	
	public Class<?> getEntityClass()
	{
		return entityClass ;
	}

	public String getDbColumnName()
	{
		return dbColumnName ;
	}
	
	public String getAttributeName()
	{
		return fieldName ;
	}
	
	public Field getField()
	{
		return field;
	}
	
	public Class<?> getFieldClass()
	{
		return field.getType() ;
	}
	
	public boolean isFieldTypePrimitive()
	{
		Class<?> type = field.getType();
		return type.isPrimitive();
	}
	
	public boolean isPrimaryKey()
	{
		return primaryKey ;
	}

	public boolean isAutoIncrementedColumn()
	{
		return autoIncrementedColumn ;
	}
	
	/**
	 * Returns the "Key Part" position for a composite key ( 1, 2, 3 ) <br>
	 * or 0 if not defined
	 * @return
	 */
	public int getKeyPart() {
		return keyPart ;
	}
	
	public void beanToTable(QueryContext queryContext, Object bean, int index )  
	{
		//--- Delegate the job to the specialized mapper
		try {
			specializedFieldMapper.beanToTable(bean, field, queryContext, index);
		} catch (IllegalAccessException e) {
			throw new TelosysPersistenceException("Cannot access entity field '" + fieldName + "' (IllegalAccessException)", e);
		}
	}
	
	public void tableToBean(QueryContext queryContext, Object bean, int index ) 
	{
		//--- Delegate the job to the specialized mapper
		try {
			specializedFieldMapper.tableToBean(bean, field, queryContext, index);
		} catch (IllegalAccessException e) {
			throw new TelosysPersistenceException("Cannot access entity field '" + fieldName + "' (IllegalAccessException)", e);
		}
	}
	
	/**
	 * Set the current value of the mapped field for the given object
	 * @param bean the bean to be updated
	 * @param value the value to set
	 */
	public void setValue(Object bean, Object value ) 
	{
		//--- Delegate the job to the specialized mapper
		try {
			specializedFieldMapper.setValue(bean, field, value);
		} catch (TelosysPersistenceException e) {
			throw e ;
		} catch (Exception e) {
			throw new TelosysPersistenceException("Cannot get value from entity field '" + fieldName + "'", e);
		}
	}
	
	/**
	 * Returns the current value of the mapped field for the given object
	 * @param bean the bean to be used
	 * @return the field value
	 */
	public Object getValue(Object bean) 
	{
		//--- Delegate the job to the specialized mapper
		try {
			return specializedFieldMapper.getValue(bean, field);
		} catch (TelosysPersistenceException e) {
			throw e ;
		} catch (Exception e) {
			throw new TelosysPersistenceException("Cannot get value from entity field '" + fieldName + "'", e);
		}
	}
	
	
	public void setAutoIncrementedValue(Object bean, long value ) 
	{
		if ( isAutoIncrementedColumn() ) 
		{
			//--- Convert value to object
			Object wrappedValue = null ;
			if ( ( specializedFieldMapper instanceof FieldMapperPrimitiveInt )
					|| ( specializedFieldMapper instanceof FieldMapperInteger ) )
			{
				//--- Integer field => CAST
				if ( value <= Integer.MAX_VALUE ) {
					wrappedValue = new Integer((int)value);
				}
				else {
			        throw new TelosysPersistenceException("Auto-incremented value out of range (" + fieldName + " / int ) ");
				}
			}
			else if ( ( specializedFieldMapper instanceof FieldMapperPrimitiveLong )
					|| ( specializedFieldMapper instanceof FieldMapperLong ) )
			{
				//--- Long field => no cast
				wrappedValue = new Long(value);
			}
			else 
			{
		        throw new TelosysPersistenceException("Field " + fieldName + " : cannot set auto-incremented value (field type is not int/long) ");
			}
				
			//--- Set value to the field
			try {				
				specializedFieldMapper.setValue(bean, field, wrappedValue);
			} catch (Exception e) {
				throw new TelosysPersistenceException("Cannot access entity field '" + fieldName + "'", e);
			}
		}
		else {
	        throw new TelosysPersistenceException("Field " + fieldName + " is not auto-incremented");
		}
	}

	public void checkNotNull( Object value ) // throws TelosysException
	{
		if ( null == value ) {
			throw new TelosysPersistenceException("Cannot set NULL in '" + fieldName + "'");
		}
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("Field mapper [ ");
		sb.append( this.entityClass.getSimpleName() ) ;
		sb.append( " - " ) ;
		sb.append( this.fieldName );
		sb.append( " ] -> '" ) ;
		sb.append( this.dbColumnName ) ;
		sb.append( "' | " ) ;
		sb.append( this.primaryKey ? "PK" : "DATA" ) ;
		sb.append( " | " ) ;
		sb.append( this.autoIncrementedColumn ? "AutoIncr" : "Not AutoIncr" ) ;
		sb.append( " | " ) ;
		sb.append( this.specializedFieldMapper != null ? this.specializedFieldMapper.getClass().getSimpleName() : "(no specilized mapper)") ;
		
		return sb.toString();
	}
	
}
